(ns server.dbm
  "This namespace contains the methods for reading the binary file"
  (:gen-class)
  (:import java.io.RandomAccessFile)
  (:require clojure.contrib.gen-html-docs))

(defn read-str-len
  "Read the next n bytes and create a string from it."
  [file n]
  (let [barray (byte-array n)]
    (.readFully file barray)
    (String. barray)))

(defn read-fields-meta
  "Reads the meta-data of all the fields"
  [file num-fields]
  (loop [n num-fields
	 result []]
    (if (= n 0)
      result
      (let [name-len (.readShort file)
	    field-name (read-str-len file name-len)
	    field-len (.readShort file)]
	(recur (dec n) (conj result [field-name field-len]))))))

(defn get-fields-names
  "Returns the name of the fields"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (let [magic-number (.readInt file)
	  offset       (.readInt file)
	  num-fields   (.readShort file)
	  fields-meta  (read-fields-meta file num-fields)]
      (loop [result []
	     fields fields-meta]
	(if (= () fields)
	  result
	  (recur (conj result (first(first fields))) (rest fields)))))))

(defn get-fields-number
  "Gets how many fields exist in every record"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (let [magic-number (.readInt file)
	  offset (.readInt file)
	  num-fields (.readShort file)]
      num-fields)))

(defn get-record-size
  "Returns the size of a record"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (let [magic-number (.readInt file)
	  offset       (.readInt file)
	  num-fields   (.readShort file)
	  fields-meta  (read-fields-meta file num-fields)]
      (loop [result 0
	     fields fields-meta]
	(if (= () fields)
	  (+ result 2)
	  (recur (+ (first(rest (first fields))) result) (rest fields)))))))


(defn get-records-offset
  "Gets the offset where the records start in the file"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (let [magic-number (.readInt file)
	  offset       (.readInt file)]
      offset)))

(defn records-number
  "Gets the number of records in the file"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (/ (- (.length file) (get-records-offset file-name)) (get-record-size file-name))))


(defn set-position-append
  "Moves the file pointer to the end of the file"
  [file]
  (let [position (.length file)]
    (.seek file position)))

(defn set-position-record
  "Moves the file pointer to the n record position"
  [file file-name RecNo]
  (let [position (+ (get-records-offset file-name) (*(get-record-size file-name) RecNo))]
    (.seek file position)))


(defn is-deleted?
  "Returns if a record is deleted"
  [file]
  (if (not (zero? (.readShort file)))
    true
    false))


(defn records-number-not-deleted
  "Gets the number of records that are not deleted in the file"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (let [offset (get-records-offset file-name)
	  size (- (get-record-size file-name) 2)
	  records (records-number file-name)]
      (.seek file offset)
      (loop [record records
	     count 0]
	(if (= record 0)
	  count
	  (if (is-deleted? file)
	    (let [next (+ size (.getFilePointer file))]  
	      (.seek file next)
	      (recur (dec record) count))
	    (let [next (+ size (.getFilePointer file))]  
	      (.seek file next)
	      (recur (dec record) (inc count)))))))))


(defn set-position-record-not-deleted
  "Gets the number of records that are not deleted in the file"
  [file file-name RecNo]
  (let [offset (get-records-offset file-name)
	size (- (get-record-size file-name) 2)]
    (.seek file offset)
    (loop [count 0]
      (if (= count RecNo)
	(if (is-deleted? file)
	  (let [next (+ size (.getFilePointer file))]  
	    (.seek file next)
	    (recur count))
	  (.seek file (- (.getFilePointer file) 2)))
	(if (is-deleted? file)
	  (let [next (+ size (.getFilePointer file))]  
	    (.seek file next)
	    (recur count))
	  (let [next (+ size (.getFilePointer file))]  
	    (.seek file next)
	    (recur (inc count))))))))

(defn get-deleted-record
  "Returns the RecNo of a deleted record"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (let [offset (get-records-offset file-name)
	  record-size (- (get-record-size file-name) 2)
	  records-max (records-number file-name)]
      (.seek file offset)
      (loop [result -1
	     count 0]
	(if (= result -1)
	  (if (>= count records-max)
	    result
	    (if (is-deleted? file)
	      count
	      (let [next (+ (.getFilePointer file) record-size)]
		(.seek file next)
		(recur result (inc count)))))
	  result)))))
  


(defn unmap
  "Returns a vector from a map in the order of the fields"
  [file-name data]
  (loop [records []
	 fields (get-fields-names file-name)]
    (if (= () fields)
      records
      (recur (conj records (data (keyword (first fields)))) (rest fields)))))

(defn write-record
  "writes a record"
  [file file-name data fields-data]
  (.writeShort file 0)
  (loop [fields fields-data
	 data-towrite (unmap file-name data)]
    (if (= () fields)
      1
      (let [towrite (first data-towrite)
	    size (first (rest (first fields)))]
	(.write file (byte-array size (.getBytes towrite)))
	(recur (rest fields) (rest data-towrite))))))

(defn read-one-record
  "Reads one individual record from the file."
  [file fields]
  (loop 
      [result {:deleted (not (zero?(.readShort file)))}
       [[fname fsize] & rest_fields] fields]
    (if fname  
      (recur (assoc result (keyword fname) (.trim (read-str-len file fsize)))
	     rest_fields)
      result)))


       
(defn read-records-not-deleted
  "Gets all the records that are not deleted in the file"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (let [magic-number (.readInt file)
	  offset       (.readInt file)
	  num-fields   (.readShort file)
	  fields-data  (read-fields-meta file num-fields)
	  record-size (- (get-record-size file-name) 2)]
      (loop [result []]
	(if (= (.getFilePointer file) (.length file))
	  result
	  (if (is-deleted? file)
	    (let [next (+ (.getFilePointer file) record-size)]
	      (.seek file next)
	      (recur result))
	    (let [next (- (.getFilePointer file) 2)]
	      (.seek file next)
	      (recur (conj result (read-one-record file fields-data))))))))))

(defn read-records
  "Gets all the records in the file"
  [file-name]
  (with-open [file (RandomAccessFile. file-name "r")]
    (let [magic-number (.readInt file)
	  offset       (.readInt file)
	  num-fields   (.readShort file)
	  fields-data  (read-fields-meta file num-fields)
	  record-size (- (get-record-size file-name) 2)]
      (loop [result []]
	(if (= (.getFilePointer file) (.length file))
	  result	 
          (recur (conj result (read-one-record file fields-data))))))))

(defn update-record
  "Updates a record based on a complete record it receives"
  [file-name RecNo data]
  (if (= (get-fields-number file-name) (count data))
    (with-open [file (RandomAccessFile. file-name "rw")]
      (if (<= RecNo (records-number-not-deleted file-name))	
	(let [number 32768
	      magic-number (.readInt file)
	      offset       (.readInt file)
	      num-fields   (.readShort file)
	      fields-data  (read-fields-meta file num-fields)]
	  (set-position-record-not-deleted file file-name (- RecNo 1))
	  (write-record file file-name data fields-data)
	  1)
	-1))
    -2))

(defn create-record
  "Creates a new record, either in a previously deleted record space or a new one if no record appears as deleted"
  [file-name data]
  (if (= (get-fields-number file-name) (count data))
    (with-open [file (RandomAccessFile. file-name "rw")]
      (let [magic-number (.readInt file)
	    offset       (.readInt file)
	    num-fields   (.readShort file)
	    fields-data  (read-fields-meta file num-fields)
	    start-record (get-deleted-record file-name)]
	(if (= start-record -1)
	  (set-position-append file)
	  (set-position-record file file-name start-record))
	(write-record file file-name data fields-data)))
    -1))


(defn delete-record
  "Puts the record in a deleted state"
  [file-name RecNo]
  (with-open [file (RandomAccessFile. file-name "rw")]
    (if (<= RecNo (records-number-not-deleted file-name))
      (let [number 32768]
	(set-position-record-not-deleted file file-name (- RecNo 1))
	(.writeShort file number) 
	1)
      -1))) 

